package zisu.netty.practice.RPC.server;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.reflect.FastClass;
import org.springframework.cglib.reflect.FastMethod;
import zisu.netty.practice.RPC.client.codec.RpcRequest;
import zisu.netty.practice.RPC.client.codec.RpcResponse;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Desc: ""
 * @Author: caixiang
 * @DATE: 2021/1/20 9:44
 */
@Slf4j
public class RpcServerHandler extends SimpleChannelInboundHandler<RpcRequest> {

    private Map<String ,Object> handlerMap;

    //为了在channelRead0 中不阻塞主线程，所以就需要new 一个线程池，子任务直接丢到线程池就ok了。
    private ThreadPoolExecutor executor = new ThreadPoolExecutor(16,16,600L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(65536));

    public RpcServerHandler(Map<String, Object> handlerMap){
        this.handlerMap = handlerMap;
    }

    public RpcServerHandler(){

    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RpcRequest rpcRequest) throws Exception {
        //异步
        executor.submit(new Runnable() {
            @Override
            public void run() {
                RpcResponse response = new RpcResponse();
                //把RpcRequest 的 id 传给RpcResponse 的id，这样两条消息都能对应上了。
                response.setRequestId(rpcRequest.getRequestId());
                try {
                    Object result = handler(rpcRequest);
                    response.setResult(result);
                }catch (Throwable throwable){
                    response.setThrowable(throwable);
                    log.error("rpc server handler request error,throwable:"+throwable);
                }

                ctx.writeAndFlush(response).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if(future.isSuccess()){
                            //afterRpcHook(后置处理器，当任务结束的时候执行的方法。)

                            System.out.println("server端，回复成功,requestId 是:"+response.getRequestId());
                        }
                    }
                });
                //1.解析rpcRequest
                //2.从handlerMap中找到具体接口（key）所绑定的实例（Bean 就是那些已经注入到Spring容器中的对象）。
                //3.通过反射cglib 调用具体方法 传递相关执行参数 执行逻辑即可
                //4.返回响应信息和调用发
            }
        });



    }


    /**
     * @param request
     * @return
     * @throws InvocationTargetException
     * 解析request 请求 获取具体的本地服务实例，并且通过反射 调用这个实例的具体方法
     */
    private Object handler(RpcRequest request) throws InvocationTargetException {
        String className = request.getClassName();
        Object serviceRef = handlerMap.get(className);
        //到这里 serviceRef 为null 就抛出异常了，然后 client 就终止连接了（抛出一个空报文 代表终止连接）
        Class<?> serviceClass = serviceRef.getClass();
        String methodName = request.getMethodName();
        Class<?>[] parameterTypes = request.getParameterTypes();
        Object[] parameters = request.getParameters();
        //JDK Reflect(JDK反射)

        //CgLib 反射 ( 通过反射调用相应方法 并传入指定参数 )
        FastClass fastClass = FastClass.create(serviceClass);
        FastMethod method = fastClass.getMethod(methodName, parameterTypes);

        //result 就是通过反射调用方法后的结果。
        Object result = method.invoke(serviceRef, parameters);
        return result;
    }

    /**
     * @param ctx
     * @param cause
     * 异常处理完毕关闭连接
     */
    //尽量用Throwable 不要用Exception 因为有些时候 Exception兜不住
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        log.error("server caught Throwable :"+cause);
        ctx.close();
    }
}
